use clap::{Args, Parser};
use serfig::{
    collectors::{from_env, from_file, from_self},
    parsers::Toml,
};

use common_base::{
    prelude::*,
    serde::{self, Deserialize, Serialize},
    LogConfig as InnerLogConfig,
};
use common_core::CoreConfig as InnerCoreConfig;

use crate::IOSP_COMMIT_VERSION;

use super::{Config as InnerConfig, DevelopConfig as InnerDevelopConfig};

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Parser)]
#[clap(about, version = &**IOSP_COMMIT_VERSION, author)]
#[serde(crate = "self::serde")]
#[serde(default)]
pub struct Config {
    /// Run a command and quit
    #[clap(long, default_value_t)]
    pub cmd: String,

    #[clap(long, short = 'c', default_value_t)]
    pub config_file: String,

    #[clap(flatten)]
    pub core: CoreConfig,

    #[clap(flatten)]
    pub develop: DevelopConfig,

    #[clap(flatten)]
    pub log: LogConfig,
}

impl Default for Config {
    fn default() -> Self {
        InnerConfig::default().into_outer()
    }
}

impl From<InnerConfig> for Config {
    fn from(inner: InnerConfig) -> Self {
        Self {
            cmd: inner.cmd,
            config_file: inner.config_file,
            core: inner.core.into(),
            develop: inner.develop.into(),
            log: inner.log.into(),
        }
    }
}

impl TryInto<InnerConfig> for Config {
    type Error = ErrorCode;

    fn try_into(self) -> Result<InnerConfig> {
        Ok(InnerConfig {
            cmd: self.cmd,
            config_file: self.config_file,
            core: self.core.try_into()?,
            develop: self.develop.try_into()?,
            log: self.log.try_into()?,
        })
    }
}

impl Config {
    /// Load will load config from file, env and args.
    ///
    /// - Load from file as default.
    /// - Load from env, will override config from file.
    /// - Load from args as finally override
    pub fn load() -> Result<Self> {
        let arg_conf = Self::parse();

        let mut builder: serfig::Builder<Self> = serfig::Builder::default();

        // Load from config file first.
        {
            let config_file = if !arg_conf.config_file.is_empty() {
                arg_conf.config_file.clone()
            } else if let Ok(path) = std::env::var("CONFIG_FILE") {
                path
            } else {
                "".to_owned()
            };

            builder = builder.collect(from_file(Toml, &config_file));
        }

        // Then, load from env.
        builder = builder.collect(from_env());

        // Finally, load from args.
        builder = builder.collect(from_self(arg_conf));

        Ok(builder.build()?)
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args)]
#[serde(crate = "self::serde")]
#[serde(default)]
pub struct CoreConfig {
    #[clap(
        long = "service_container_dir",
        default_value = "./dev-tools/deploy/service_containers"
    )]
    #[serde(alias = "service_container_dir")]
    pub service_container_dir: String,

    #[clap(
        long = "service_dylib_dir",
        default_value = "./dev-tools/deploy/service_dylibs"
    )]
    #[serde(alias = "service_dylib_dir")]
    pub service_dylib_dir: String,

    #[clap(
        long = "db_config_file",
        default_value = "./dev-tools/deploy/config/db_config.toml"
    )]
    #[serde(alias = "db_config_file")]
    pub db_config_file: String,

    #[clap(
        long = "eai_server_file",
        default_value = "./dev-tools/deploy/config/eai_server.toml"
    )]
    #[serde(alias = "eai_server_file")]
    pub eai_server_file: String,

    #[clap(long = "local_eai_server_id", default_value = "")]
    #[serde(alias = "local_eai_server_id")]
    pub local_eai_server_id: String,
}

impl Default for CoreConfig {
    fn default() -> Self {
        InnerCoreConfig::default().into()
    }
}

impl TryInto<InnerCoreConfig> for CoreConfig {
    type Error = ErrorCode;

    fn try_into(self) -> Result<InnerCoreConfig> {
        Ok(InnerCoreConfig {
            service_container_dir: self.service_container_dir,
            service_dylib_dir: self.service_dylib_dir,
            db_config_file: self.db_config_file,
            eai_server_file: self.eai_server_file,
            local_eai_server_id: self.local_eai_server_id,
        })
    }
}

impl From<InnerCoreConfig> for CoreConfig {
    fn from(inner: InnerCoreConfig) -> Self {
        Self {
            service_container_dir: inner.service_container_dir,
            service_dylib_dir: inner.service_dylib_dir,
            db_config_file: inner.db_config_file,
            eai_server_file: inner.eai_server_file,
            local_eai_server_id: inner.local_eai_server_id,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args)]
#[serde(crate = "self::serde")]
#[serde(default)]
pub struct DevelopConfig {
    /// Tenant id for get the information from the MetaSrv.
    #[clap(long, default_value = "admin")]
    pub tenant_id: String,

    /// ID for construct the cluster.
    #[clap(long, default_value_t)]
    pub cluster_id: String,

    #[clap(long, default_value_t)]
    pub num_cpus: u64,

    #[clap(long, default_value = "127.0.0.1")]
    pub http_handler_host: String,

    #[clap(long, default_value = "8000")]
    pub http_handler_port: u16,

    #[clap(long, default_value = "10000")]
    pub http_handler_result_timeout_millis: u64,
}

impl Default for DevelopConfig {
    fn default() -> Self {
        InnerDevelopConfig::default().into()
    }
}

impl TryInto<InnerDevelopConfig> for DevelopConfig {
    type Error = ErrorCode;

    fn try_into(self) -> Result<InnerDevelopConfig> {
        Ok(InnerDevelopConfig {
            tenant_id: self.tenant_id,
            cluster_id: self.cluster_id,
            num_cpus: self.num_cpus,
            http_handler_host: self.http_handler_host,
            http_handler_port: self.http_handler_port,
            http_handler_result_timeout_millis: self.http_handler_result_timeout_millis,
        })
    }
}

impl From<InnerDevelopConfig> for DevelopConfig {
    fn from(inner: InnerDevelopConfig) -> Self {
        Self {
            tenant_id: inner.tenant_id,
            cluster_id: inner.cluster_id,
            num_cpus: inner.num_cpus,
            http_handler_host: inner.http_handler_host,
            http_handler_port: inner.http_handler_port,
            http_handler_result_timeout_millis: inner.http_handler_result_timeout_millis,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args)]
#[serde(crate = "self::serde")]
#[serde(default)]
pub struct LogConfig {
    /// Log level <DEBUG|INFO|ERROR>
    #[clap(long = "log-level", default_value = "INFO")]
    #[serde(alias = "log_level")]
    pub level: String,

    /// Log file dir
    #[clap(long = "log-dir", default_value = "./.iosp/logs")]
    #[serde(alias = "log_dir")]
    pub dir: String,
}

impl Default for LogConfig {
    fn default() -> Self {
        InnerLogConfig::default().into()
    }
}

impl TryInto<InnerLogConfig> for LogConfig {
    type Error = ErrorCode;

    fn try_into(self) -> Result<InnerLogConfig> {
        Ok(InnerLogConfig {
            level: self.level,
            dir: self.dir,
        })
    }
}

impl From<InnerLogConfig> for LogConfig {
    fn from(inner: InnerLogConfig) -> Self {
        Self {
            level: inner.level,
            dir: inner.dir,
        }
    }
}
